package org.judo.generate.mvc;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Properties;
import java.util.Stack;

import org.judo.generate.GenerationException;
import org.judo.generate.java.ClassGenerator;
import org.judo.generate.java.Field;
import org.judo.generate.java.Method;
import org.judo.generate.java.SingleStatement;
import org.judo.util.JavaNameConversion;
import org.judo.util.Util;
import org.judo.utility.FileDataReader;


public class MVCGenerator
{
	Stack<Parser> scopeStack = new Stack<Parser>();
	String fileName;
	String files = "";
	boolean writeFiles = true;
	
	public void writeFiles(boolean status)
	{
		writeFiles = status;
	}
	
	public MVCGenerator(String fileName)
	{
		this.fileName = fileName;
	}
	
	public boolean parseScript()
		throws Exception
	{
		String scriptCode = null;
		
		if (fileName.startsWith("mvc_code:"))
			scriptCode = fileName.replace("mvc_code:","");
		else
			scriptCode = FileDataReader.getFileData("/mvc_scripts/"+fileName+".mvc");
		
		scriptCode += "\n"+"/SCRIPT_END";
		
		if (scriptCode == null)
			throw new Exception("Error reading in script.  Script data could not be found.  This is probablly a bug in the generator.");
		
		String lines[] = scriptCode.split("\n");
		
		FileParser fileParser = new FileParser();
		scopeStack.push(fileParser);
		
		int lineNum = 1;
		
		for (String line: lines)
		{
			try
			{
				line = line.trim();
				
				if (line.startsWith("#"))
					continue;
				
				Parser parser = scopeStack.peek();
				
				if (parser == null)
					throw new Exception("Syntax Error at line "+lineNum+": "+line);
				
				parser.parseNextLine(line);
				
				if (parser.isFinished())
				{
					scopeStack.pop();
					continue;
				}
				
				if (parser.needsNestedParser())
				{
					Parser nestedParser = parser.getNestedParser();
					scopeStack.push(nestedParser);
					nestedParser.setInitLine(line);
				}
			}
			catch(ParseException e)
			{
				System.out.println("Syntax Error at line "+lineNum+": "+e.getParseError()+": "+line);
				return false;
				
			}
			finally
			{
				lineNum++;
			}
		}
		
		if (generateCode(fileParser.getParseResults()))
		{
			writeFile(new File("./script/saved/last.txt"),files);
			return true;
		}
		else
		{
			writeFile(new File("./script/saved/last.txt"),files);
			return false;
		}
	}

	private boolean generateCode(ParseResults parseResults)
		throws ParseException
	{
		HashMap<String,Method> alreadyAddedMethods = new HashMap<String,Method>(); 	
		HashMap<String,ClassGenerator> controllers = new HashMap<String,ClassGenerator>();
		HashMap<String,ClassGenerator> models = new HashMap<String,ClassGenerator>();

		if (!generateModels(parseResults,models))
			return false;
		
		if (!generateViews(parseResults,controllers,alreadyAddedMethods,models))
			return false;

		if (!generateActions(parseResults,controllers,alreadyAddedMethods,models))
			return false;
		
		addModelImports(models,controllers);
		
		if (!generateClasses(controllers))
			return false;

		return true;
	}
	
	private void addModelImports(HashMap<String, ClassGenerator> models, HashMap<String, ClassGenerator> controllers)
	{
		Iterator<ClassGenerator> contollerClasses = controllers.values().iterator();
		
		while (contollerClasses.hasNext())
		{
			ClassGenerator controller = contollerClasses.next();
			
			Iterator<ClassGenerator> modelClasses = models.values().iterator();
			
			while (modelClasses.hasNext())
			{
				ClassGenerator model = modelClasses.next();
				controller.addImport(model.getPackageName()+"."+model.getClassName());
			}
		}
	}

	private boolean generateModels(ParseResults parseResults, HashMap<String, ClassGenerator> models)
	{
		Properties props = new Properties();
		
		try{
			props.load(new FileInputStream(new File("./config/runtime/judo_config.properties")));
		} 
		catch (Exception e1){
			System.out.println("Could not find property file: [project]/config/runtime/judo_config.properties because: "+e1);
			return false;
		}
		
		String modelPath = (String)props.get("judo.models.base.classpath");
		
		if (modelPath == null || modelPath.equals("NOT_DEFINED"))
		{
			System.out.println("Error: The 'judo.models.base.classpath' property must be set before code can be generated.  Set this property in the file: [project]/config/runtime/judo_config.properties");
			return false;
		}
		
		Iterator<ModelResult> modelList = parseResults.models.values().iterator();
		
		while (modelList.hasNext())
		{
			ModelResult modelResult = modelList.next();
			
			ClassGenerator modelClass = new ClassGenerator();
			modelClass.setClassName(modelResult.name);
			modelClass.setPackageName(modelPath);
			modelClass.addImport("org.judo.propertyobject.PropertyObject");
			modelClass.addImport("java.util.*");
			modelClass.setSuperClass("PropertyObject");
			
			Iterator<PropertyResult> propList = modelResult.props.values().iterator();
			
			while (propList.hasNext())
			{
				PropertyResult prop = propList.next();
				
				if (prop.type.equals("String"))
					modelClass.addProperty("String",prop.name);
				else if (prop.type.equals("date"))
					modelClass.addProperty("Date",prop.name);
				else if (prop.type.equals("int"))
					modelClass.addProperty("int",prop.name);
				else if (prop.type.equals("float"))
					modelClass.addProperty("float",prop.name);
			}
			
			models.put(modelResult.name,modelClass);
		}
		
		if (!generateClasses(models))
			return false;
		
		return true;
	}

	protected boolean generateClasses(HashMap<String,ClassGenerator> controllers)
	{
		Iterator<ClassGenerator> contollerClasses = controllers.values().iterator();
		
		while (contollerClasses.hasNext())
		{
			ClassGenerator cur = contollerClasses.next();
			
			try
			{
				String text = cur.generate();
				String packageDirs = cur.getPackageName().replace(".","/");
				
				File packagePath = new File("./src/"+packageDirs);
				
				if (!packagePath.exists())
				{
					packagePath.mkdirs();
				}
					
				File classFile = new File("./src/"+packageDirs+"/"+cur.getClassName()+".java");
				writeFile(classFile,text);
			} 
			catch (GenerationException e){
				System.out.println("Error generating '"+cur.getClassName()+"' class.  Unable to continue: "+e);
				return false;
			}
		}
		
		return true;
	}
	
	private boolean generateActions(ParseResults parseResults, HashMap<String,ClassGenerator> controllers, 
			HashMap<String, Method> alreadyAddedMethods, HashMap<String, ClassGenerator> models)
	{
		Iterator<ActionCodeResult> actionResults = parseResults.actionResults.values().iterator();
		
		Properties props = new Properties();
		
		try{
			props.load(new FileInputStream(new File("./config/runtime/judo_config.properties")));
		} 
		catch (Exception e1){
			System.out.println("Could not find property file: [project]/config/runtime/judo_config.properties because: "+e1);
			return false;
		}
		
		String controllerPath = (String)props.get("judo.controllers.base.classpath");
		
		if (controllerPath == null || controllerPath.equals("NOT_DEFINED"))
		{
			System.out.println("Error: The 'judo.controllers.base.classpath' property must be set before code can be generated.  Set this property in the file: [project]/config/runtime/judo_config.properties");
			return false;
		}
		
		while (actionResults.hasNext())
		{
			ActionCodeResult action = actionResults.next();
			
			String className = JavaNameConversion.getClassName(action.contollerName);
			ClassGenerator controllerClass = controllers.get(className);
			
			if (controllerClass == null)
			{
				controllerClass = new ClassGenerator();
				controllerClass.setClassName(className);
				controllerClass.addImport("org.judo.controller.PageController");
				controllerClass.addImport("org.judo.database.Database");
				controllerClass.addImport("org.judo.propertyobject.PropertyObject");
				controllerClass.addImport("java.util.*");
				controllerClass.setSuperClass("PageController");
				controllerClass.setPackageName(controllerPath);
				
				Field appDB = new Field(false,"private","Database","appDB",null,1);
				controllerClass.addField(appDB);
				
				controllers.put(className,controllerClass);
			}
			
			String actionName = JavaNameConversion.getMemberName(action.actionName);
			Method actionMethod = alreadyAddedMethods.get(className+"."+actionName);
				
			if (actionMethod == null)
			{	
				actionMethod = new Method();
				actionMethod.setMethodName(actionName);
				actionMethod.addExceptions("Exception");
				controllerClass.addMethod(actionMethod);
				alreadyAddedMethods.put(className+"."+actionName,actionMethod);
			}
			
			for (String contextVar:action.contextVars)
			{
				if (contextVar.contains(","))
				{
					String parts[] = contextVar.split(",");
					
					String type = parts[0].trim();
					String name = parts[1].trim();
					
					controllerClass.addProperty(type,name);
				}
				else
				{
					controllerClass.addProperty("String",contextVar);
				}
			}
		
			if (action.javaCode != null)
			{
				actionMethod.addFreeFormCode(action.javaCode);
			}
		}
		
		return true;
	}
	
	protected boolean generateViews(
			ParseResults parseResults, HashMap<String,ClassGenerator> controllers, 
			HashMap<String,Method> alreadyAddedMethods, HashMap<String, ClassGenerator> models)
				throws ParseException
	{
		Properties props = new Properties();
		
		try{
			props.load(new FileInputStream(new File("./config/runtime/judo_config.properties")));
		} 
		catch (Exception e1){
			System.out.println("Could not find property file: [project]/config/runtime/judo_config.properties because: "+e1);
			return false;
		}
		
		String controllerPath = (String)props.get("judo.controllers.base.classpath");
		
		if (controllerPath == null || controllerPath.equals("NOT_DEFINED"))
		{
			System.out.println("Error: The 'judo.controllers.base.classpath' property must be set before code can be generated.  Set this property in the file: [project]/config/runtime/judo_config.properties");
			return false;
		}
		
		Iterator<ViewResult> viewResults = parseResults.views.values().iterator();
		
		while (viewResults.hasNext())
		{
			ViewResult view = viewResults.next();
			createJSP(view);
			
			String className = JavaNameConversion.getClassName(view.contollerName);
			ClassGenerator controllerClass = controllers.get(className);
			
			if (!view.genConroller)
				continue;
			
			if (controllerClass == null)
			{
				controllerClass = new ClassGenerator();
				controllerClass.setClassName(className);
				controllerClass.addImport("org.judo.controller.PageController");
				controllerClass.addImport("org.judo.database.Database");
				controllerClass.addImport("org.judo.propertyobject.PropertyObject");
				controllerClass.addImport("java.util.*");
				controllerClass.setSuperClass("PageController");
				controllerClass.setPackageName(controllerPath);
				
				Field appDB = new Field(false,"private","Database","appDB",null,1);
				controllerClass.addField(appDB);
				
				controllers.put(className,controllerClass);
			}

			if (view.path != null)
				controllerClass.setPackageName(controllerPath+"."+(view.path.replace("/",".")));
			
			if (view.scope.equals("session"))
			{
				controllerClass.addImport("org.judo.components.state.SessionScope");
				controllerClass.addAnnototation("@SessionScope");
			}
			else if (view.scope.startsWith("group"))
			{
				controllerClass.addImport("org.judo.components.state.GroupScope");
				String parts[] = view.scope.split(",");
				controllerClass.addAnnototation("@GroupScope(\""+parts[1]+"\")");
			}
				
			String actionName = JavaNameConversion.getMemberName(view.actionName);
			Method actionMethod = alreadyAddedMethods.get(className+"."+actionName);
			
			if (actionMethod == null)
			{
				actionMethod = new Method();
				actionMethod.setMethodName(actionName);
				actionMethod.addExceptions("Exception");
				controllerClass.addMethod(actionMethod);
				alreadyAddedMethods.put(className+"."+actionName,actionMethod);
			}
			
			if (view.actionCode != null)
			{
				actionMethod.addFreeFormCode(view.actionCode.javaCode);
			}
			
			for (String contextVar:view.contextVars)
			{
				if (contextVar.contains(","))
				{
					String parts[] = contextVar.split(",");
					
					String type = parts[0].trim();
					String name = parts[1].trim();
					
					if (formDoesntDefineProperty(name,className,view))
						controllerClass.addProperty(type,name);
				}
				else
				{
					if (formDoesntDefineProperty(contextVar,className,view))
						controllerClass.addProperty("String",contextVar);
				}
			}
			
			for (String importValue:view.imports)
				controllerClass.addImport(importValue);
			
			for (String annotation:view.methodAnnotations)
				actionMethod.addAnnototation(annotation);
			
			for (DatatableInfo dt:view.dataTables)
			{
				controllerClass.addProperty("ArrayList",dt.items,"new ArrayList()");
			}
			
			if (!generateFormCode(view,controllers,controllerPath,alreadyAddedMethods,models))
				return false;
			
			if (view.menuName != null && view.menuDisplay != null)
			{
				MenuManipulator.addMenuItem(view.menuName,view.menuDisplay,"/"+view.contollerName+"/"+view.actionName+".do");
			}
		}
		
		return true;
	}
	
	private boolean formDoesntDefineProperty(String name, String className,ViewResult view)
	{
		for (FormInfo form:view.forms)
		{
			String controller = JavaNameConversion.getClassName(form.controller);
			
			for (String contextVar:form.contextVars)
			{	
				if (contextVar.contains(","))
				{
					String parts[] = contextVar.split(",");
					
					String type = parts[0].trim();
					String varName = parts[1].trim();
					
					if (varName.equals(name) && className.equals(controller))
						return false;
				}
				else
				{
					if (contextVar.equals(name) && className.equals(controller))
						return false;
				}
			}
		}
		
		return true;
	}

	protected boolean generateFormCode(
			ViewResult view,
			HashMap<String,ClassGenerator> controllers,
			String controllerPath,
			HashMap<String,Method> alreadyAddedMethods,
			HashMap<String, ClassGenerator> models)
	{
		for (FormInfo form:view.forms)
		{
			String action = JavaNameConversion.getMemberName(form.action);
			String controller = JavaNameConversion.getClassName(form.controller);
			
			ClassGenerator formControllerClass = controllers.get(controller);
			
			if (formControllerClass == null)
			{
				formControllerClass = new ClassGenerator();
				formControllerClass.setClassName(controller);
				formControllerClass.addImport("org.judo.controller.PageController");
				formControllerClass.addImport("org.judo.database.Database");
				formControllerClass.addImport("org.judo.propertyobject.PropertyObject");
				formControllerClass.setSuperClass("PageController");
				formControllerClass.setPackageName(controllerPath);
				formControllerClass.addImport("java.util.*");
				
				Field appDB = new Field(false,"private","Database","appDB",null,1);
				formControllerClass.addField(appDB);
				
				controllers.put(controller,formControllerClass);
			}
			
			if (form.path != null)
				formControllerClass.setPackageName(controllerPath+"."+(form.path.replace("/",".")));
			
			formControllerClass.setPackageName(controllerPath);
			
			Method actionMethod = alreadyAddedMethods.get(controller+"."+action);
			
			if (actionMethod == null)
			{
				actionMethod = new Method();
				actionMethod.setMethodName(action);
				actionMethod.addExceptions("Exception");
				formControllerClass.addMethod(actionMethod);
				alreadyAddedMethods.put(controller+"."+action,actionMethod);
			}
			
			for(InputInfo input:form.inputs)
			{
				if (input.type.equals("string"))
					formControllerClass.addProperty("String",input.name);
				else if (input.type.equals("int"))
					formControllerClass.addProperty("int",input.name);
				else if (input.type.equals("float"))
					formControllerClass.addProperty("float",input.name);
				else if (input.type.equals("date"))
					formControllerClass.addProperty("Date",input.name);
			}
			
			for (String contextVar:form.contextVars)
			{	
				if (contextVar.contains(","))
				{
					String parts[] = contextVar.split(",");
					
					String type = parts[0].trim();
					String name = parts[1].trim();
					
					formControllerClass.addProperty(type,name);
				}
				else	
					formControllerClass.addProperty("String",contextVar);
			}
			
			if (form.actionCode != null)
			{
				actionMethod.addFreeFormCode(form.actionCode.javaCode);
			}
			
			if(form.forward != null)
			{
				actionMethod.addStatement(new SingleStatement(2,"showPage(\"/"+form.forward+".do\");"));
			}
		}
		
		return true;
	}

	protected String getModelPath()
	{
		Properties props = new Properties();
		
		try{
			props.load(new FileInputStream(new File("./config/runtime/judo_config.properties")));
		} 
		catch (Exception e1){
			System.out.println("Could not find property file: [project]/config/runtime/judo_config.properties because: "+e1);
		}
		
		String modelPath = (String)props.get("judo.models.base.classpath");
		
		
		return modelPath;
	}
	
	protected void createJSP(ViewResult view)
	{
		String name = view.actionName+".jsp";
		
		if (view.path != null)
			name = view.path+"/"+name;
		
		String paths[] = name.split("/");
		
		if (!paths[paths.length-1].endsWith(".jsp"))
		{
			System.out.println("Does not end with '.jsp': '"+name+"'.  Page not created.");
			return;
		}
		
		String dirs = "";
		
		for (int i=0;i<paths.length-1;i++)
		{
			dirs += "/"+paths[i];
		}
		
		File dir = new File("./webfiles/"+dirs);
		
		if (!dir.exists())
		{
			dir.mkdirs();
		}
		
		String text;

		try
		{
			text = FileDataReader.getFileData("/templates/"+view.pageTemplate);
			text = text.replace("##MAIN##",view.html);
			text = text.replace("##TITLE##",view.title);
			text = text.replace("ROOT","${rootContext}");
		}
		catch(Exception e)
		{
			System.out.println("Error creating JSP: "+e);
			e.printStackTrace();
			return;
		}
		
		File jspFile = new File("./webfiles/"+name);
		writeFile(jspFile,text);
		
	}
	
	protected void writeFile(File file,String text)
	{
		FileWriter writer = null;
		
		try
		{
			if (!file.getName().equals("last.txt"))
				files += file.getAbsolutePath()+"00000";
			
			writer = new FileWriter(file);
			writer.write(text);
			
			if (!file.getName().equals("last.txt"))
				System.out.println("File created: "+file.getName());
		} 
		catch (Exception e)
		{
			System.out.println("Error creating File: "+file.getName()+":"+e);
			e.printStackTrace();
			return;
		}
		finally
		{
			try{writer.close();}catch(Exception e){}
		}
	}
}

